#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

#include "web-socket.h"

#define MAX_CACHE_SIZE 1049000
#define MAX_OBJECT_SIZE 102400

static const char *user_agent_hdr =
    "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3) Gecko/20120305 "
    "Firefox/10.0.3\r\n";
static const char *conn_hdr = "Connection: close\r\n";
static const char *prox_hdr = "Proxy-Connection: close\r\n";

void doit(int fd);
void clienterror(int fd, char *cause, char *errnum, char *shortmsg,
                 char *longmsg);
void parse_uri(char *uri, char *hostname, char *path, int *port);
void build_requesthdrs(header *rp, char *newreq, char *hostname);
int open_clientfd(char *hostname, char *port);

/**
 *  https://www.jianshu.com/p/a501d0c2f131
 *  https://github.com/Zuixie?tab=repositories
 */
int main(int argc, char **argv) {
    int listenfd, connfd;
    socklen_t clientlen;
    struct sockaddr_in clientaddr;

    signal(SIGPIPE, SIG_IGN);

    listenfd = openListenfd(8080);
    if (listenfd < 0) {
        unixerror("Listen error");
        exit(0);
    }

    while (1) {
        printf("listening..\n");
        clientlen = sizeof(clientaddr);
        connfd = accept(listenfd, (struct sockaddr *) &clientaddr, &clientlen);
        if (connfd < 0) {
            unixerror("Accept error");
        }

        doit(connfd);
    }
}

/* $begin doit */
void doit(int client_fd) {
    int endserver_fd;
    char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
    char hostname[MAXLINE], path[MAXLINE];
    header *clientHeader, *serverHeader;
    int port;

    clientHeader = initheader(client_fd);

    readline(clientHeader, buf);

    sscanf(buf, "%s %s %s", method, uri, version);
    if (strcasecmp(method, "GET")) {
        clienterror(client_fd, method, "501", "Not Implemented",
                    "Proxy Server does not implement this method");
        return;
    }

    parse_uri(uri, hostname, path, &port);
    char port_str[10];
    sprintf(port_str, "%d", port);
    endserver_fd = open_clientfd(hostname, port_str);
    if (endserver_fd < 0) {
        printf("connection failed\n");
        return;
    }

    char newreq[MAXLINE];
    sprintf(newreq, "GET %s HTTP/1.0\r\n", path);
    build_requesthdrs(clientHeader, newreq, hostname);

    writen(endserver_fd, newreq, strlen(newreq));      
    
    serverHeader = initheader(endserver_fd);
    int n;
    while ((n = readline(serverHeader, buf)) > 0) {
        writen(client_fd, buf, n);
    }
}

void parse_uri(char *uri, char *hostname, char *path, int *port) {
    *port = 80;
    char *pos1 = strstr(uri, "//");
    if (pos1 == NULL) {
        pos1 = uri;
    } else {
        pos1 += 2;
    }

    printf("parse uri pos1 %s\n", pos1);

    char *pos2 = strstr(pos1, ":");
    if (pos2 != NULL) {
        *pos2 = '\0';
        strncpy(hostname, pos1, MAXLINE);
        sscanf(pos2 + 1, "%d%s", port, path);
        *pos2 = ':';
    } else {
        pos2 = strstr(pos1, "/"); 
        if (pos2 == NULL) { 
            strncpy(hostname, pos1, MAXLINE);
            strcpy(path, "");
            return;
        }
        *pos2 = '\0';
        strncpy(hostname, pos1, MAXLINE);
        *pos2 = '/';
        strncpy(path, pos2, MAXLINE);
    }
}

void build_requesthdrs(header *rp, char *newreq, char *hostname) {
    char buf[MAXLINE];

    while (readline(rp, buf) > 0) {
        if (!strcmp(buf, "\r\n")) break;
        if (strstr(buf, "Host:") != NULL) continue;
        if (strstr(buf, "User-Agent:") != NULL) continue;
        if (strstr(buf, "Connection:") != NULL) continue;
        if (strstr(buf, "Proxy-Connection:") != NULL) continue;

        sprintf(newreq, "%s%s", newreq, buf);
    }
    sprintf(newreq, "Host: %s:%s\r\n", newreq, hostname);
    sprintf(newreq, "%s%s%s%s", newreq, user_agent_hdr, conn_hdr, prox_hdr);
    sprintf(newreq, "%s\r\n", newreq);
}

int open_clientfd(char *hostname, char *port) {
    int clientfd, rc;
    struct addrinfo hints, *listp, *p;

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_socktype = SOCK_STREAM; 
    hints.ai_flags = AI_NUMERICSERV;
    hints.ai_flags |= AI_ADDRCONFIG;
    if ((rc = getaddrinfo(hostname, port, &hints, &listp)) != 0) {
        fprintf(stderr, "getaddrinfo failed (%s:%s): %s\n", hostname, port, gai_strerror(rc));
        return -2;
    }
  
    for (p = listp; p; p = p->ai_next) {
        if ((clientfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) 
            continue; 

        if (connect(clientfd, p->ai_addr, p->ai_addrlen) != -1) 
            break; 
        if (close(clientfd) < 0) {
            fprintf(stderr, "open_clientfd: close failed: %s\n", strerror(errno));
            return -1;
        } 
    } 

    freeaddrinfo(listp);
    if (!p) {
        return -1;
    } else { 
        return clientfd;
    }
}